﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GraphosData;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Media.Animation;
using System.ComponentModel;
using System.Windows.Controls;

namespace GraphosControls
{

    public class TransitionAnimationCompleteEventArgs : EventArgs
    {
        public TransitionAnimationCompleteEventArgs(IEnumerable<UIElement> elems)
        {
            ElemetsDoneAnimating = elems;
        }
        public IEnumerable<UIElement> ElemetsDoneAnimating { get; private set; }
    }

    public class GraphViewerAnimation : INotifyPropertyChanged
    {
        private static class Const
        {
            public static TimeSpan CreateNodeTime = TimeSpan.FromSeconds(1.5);
            public static TimeSpan CreateEdgeTime = TimeSpan.FromSeconds(1.0);
            public static TimeSpan CreateLabelTime = TimeSpan.FromSeconds(1.5);
            public static TimeSpan DestroyNodeTime = TimeSpan.FromSeconds(0.5);
            public static TimeSpan DestroyEdgeTime = TimeSpan.FromSeconds(0.75);
            public static TimeSpan DestroyLabelTime = TimeSpan.FromSeconds(0.5);
        }
        private class DummyNode
        {
            public double Charge { get; set; }
            public Point Position { get; set; }

        }

        public GraphViewerAnimation()
        {
            m_animCompleteTimer = new DispatcherTimer();
            m_animCompleteTimer.Tick += TransitionAnimationsComplete_Tick;
            m_animCompleteTimer.Interval = TimeSpan.FromSeconds(1.5);
            m_timeStep = 0.03;
            m_mouseCharge = new DummyNode();
            m_centerSpring = new DummyNode();

            m_spring = 20;
            m_damping = 0.75;
            m_edgeLength = 50;
            m_charge = 400;
        }

        //public bool MouseRepulse
        //{
        //    get { return m_mouseRepulse; }
        //    set { m_mouseRepulse = value; }
        //}
        //public bool MouseAttract
        //{
        //    get { return m_mouseAttract; }
        //    set { m_mouseAttract = value; }
        //}
        //public bool IsInitialized
        //{
        //    get { return m_animationInitialized; }
        //}

        private VisualNode m_dragNode;
        private DummyNode m_dragDummy;
        private Point m_dragPosition;
        private bool m_dragging = false;

        

        public VisualNode DragNode
        {
            get { return m_dragNode; }
            set
            {
                if (m_dragNode != value)
                {
                    if (value == null)
                    {
                        ReleaseDragNode();
                        return;
                    }

                    if (m_dragNode != null)
                        ReleaseDragNode();

                    SetDragNode(value);
                }
            }
        }
        public Point DragPosition
        {
            get { return m_dragPosition; }
            set
            {
                m_dragPosition = value;
                m_dragDummy.Position = m_dragPosition;
                Start(m_rootNode, m_width, m_height);
            }
        }

        private void SetDragNode(VisualNode node)
        {
            m_dragNode = node;
            m_dragDummy = new DummyNode();
            m_dragging = m_dragNode.CaptureMouse();


            if (!m_dragging)
                ReleaseDragNode();
        }
        private void ReleaseDragNode()
        {
            m_dragNode.ReleaseMouseCapture();
            m_dragNode = null;
            m_dragPosition = new Point();
            m_dragging = false;
            m_dragDummy = null;
        }


        List<VisualEdge> m_edges = new List<VisualEdge>();

        public void SetEdges(List<VisualEdge> edges)
        {
            m_edges = edges;
        }



        #region Transition Animation Logic

        public delegate void AnimationCompleteEventHandler(object sender, TransitionAnimationCompleteEventArgs e);
        public event AnimationCompleteEventHandler TransitionAnimationComplete;
        private void OnTransitionAnimationComplete(IList<UIElement> elems)
        {
            if (TransitionAnimationComplete != null && elems.Count > 0)
            {
                TransitionAnimationComplete(this, new TransitionAnimationCompleteEventArgs(elems));
            }
            m_elementsBeingAnimated = new List<UIElement>();
        }

        private void TransitionAnimationsComplete_Tick(object sender, EventArgs e)
        {
            DispatcherTimer timer = sender as DispatcherTimer;
            timer.Stop();

            OnTransitionAnimationComplete(m_elementsBeingAnimated);
        }

        public void ApplyCreateNodeAnimation(VisualNode node)
        {
            DoubleAnimation opacity = new DoubleAnimation();
            opacity.To = 1.0;
            opacity.Duration = Const.CreateNodeTime;
            opacity.AccelerationRatio = 0.75;
            opacity.DecelerationRatio = 0.25;
            opacity.BeginTime = TimeSpan.FromSeconds(0.0);
            opacity.Freeze();
            node.BeginAnimation(VisualNode.OpacityProperty, opacity);

        }
        public void ApplyCreateEdgeAnimation(UIElement elem)
        {
            DoubleAnimation opacity = new DoubleAnimation();
            opacity.To = 1.0;
            opacity.Duration = Const.CreateEdgeTime;
            opacity.AccelerationRatio = 0.75;
            opacity.DecelerationRatio = 0.25;
            opacity.BeginTime = TimeSpan.FromSeconds(0.0);
            opacity.Freeze();
            elem.BeginAnimation(UIElement.OpacityProperty, opacity);
        }
        public void ApplyCreateLabelAnimation(UIElement elem)
        {
            DoubleAnimation opacity = new DoubleAnimation();
            opacity.To = 1.0;
            opacity.Duration = Const.CreateLabelTime;
            opacity.AccelerationRatio = 0.75;
            opacity.DecelerationRatio = 0.25;
            opacity.BeginTime = TimeSpan.FromSeconds(0.0);
            opacity.Freeze();
            elem.BeginAnimation(UIElement.OpacityProperty, opacity);
        }
        public void ApplyDestroyNodeAnimation(VisualNode node)
        {
            m_elementsBeingAnimated.Add(node);
            m_animCompleteTimer.Start();

            DoubleAnimation opacity = new DoubleAnimation();
            opacity.To = 0.0;
            opacity.AccelerationRatio = 0.25;
            opacity.DecelerationRatio = 0.75;
            opacity.Duration = Const.DestroyNodeTime;
            opacity.Freeze();
            node.BeginAnimation(VisualNode.OpacityProperty, opacity);
        }
        public void ApplyDestroyEdgeAnimation(UIElement elem)
        {
            m_elementsBeingAnimated.Add(elem);
            m_animCompleteTimer.Start();

            DoubleAnimation opacity = new DoubleAnimation();
            opacity.To = 0.0;
            opacity.AccelerationRatio = 0.25;
            opacity.DecelerationRatio = 0.75;
            opacity.Duration = Const.DestroyEdgeTime;
            opacity.Freeze();
            elem.BeginAnimation(UIElement.OpacityProperty, opacity);
        }
        public void ApplyDestroyLabelAnimation(UIElement elem)
        {
            m_elementsBeingAnimated.Add(elem);
            m_animCompleteTimer.Start();

            DoubleAnimation opacity = new DoubleAnimation();
            opacity.To = 0.0;
            opacity.AccelerationRatio = 0.25;
            opacity.DecelerationRatio = 0.75;
            opacity.Duration = Const.DestroyLabelTime;
            opacity.Freeze();
            elem.BeginAnimation(UIElement.OpacityProperty, opacity);
        }
        private DispatcherTimer m_animCompleteTimer;
        private List<UIElement> m_elementsBeingAnimated = new List<UIElement>();

        #endregion

        #region Position Animation Logic

        public event RoutedEventHandler PositionAnimationComplete;
        private void OnPositionAnimationComplete()
        {
            if (PositionAnimationComplete != null)
                PositionAnimationComplete(this, new RoutedEventArgs());
        }

        public void Start(VisualNode rootNode, double width, double height)
        {

            if (!m_isAnimating)
            {
                // add the handler for the animation
                CompositionTarget.Rendering += PositionAnimation_Rendering;
                m_isAnimating = true;
            }
            if (m_rootNode != rootNode)
            {
                m_rootNode = rootNode;

                m_numberEdges = rootNode.AdjacentVisualNodes.Count + 1;
                foreach (VisualNode node in rootNode.AdjacentVisualNodes)
                    m_numberEdges += node.AdjacentVisualNodes.Count;

                // set the cut off value
                m_kineticEnergyLimit = Math.Pow(m_numberEdges, 1.5);
            }

            foreach (VisualEdge edge in m_edges)
            {
                edge.CenterNode.Charge = m_charge;
                edge.CenterNode.Mass = 1.0;
                edge.ControlNodeOne.Charge = m_charge;
                edge.ControlNodeOne.Mass = 1.0;
            }

            // set the new width and height
            m_width = width;
            m_height = height;

            // set the mouse charge and center spring values
            m_mouseCharge.Charge = m_charge;
            m_centerSpring.Position = new Point(m_width / 2, m_height / 2);

            // set all the node charges
            foreach (var node in m_rootNode.AdjacentVisualNodes)
            {
                node.Mass = 1.0;
                node.Charge = m_charge;
            }
            
            m_rootEdgeLength = Math.Sqrt(Math.Pow(width, 2) + Math.Pow(height, 2)) / 6;
        }
        public void Pause()
        {
            CompositionTarget.Rendering -= PositionAnimation_Rendering;
            OnPositionAnimationComplete();
            m_isAnimating = false;
        }
        public void Stop()
        {
            foreach (var node in m_rootNode.AdjacentVisualNodes)
            {
                node.ResetKeneticValues();
            }
            foreach (VisualEdge edge in m_edges)
            {
                edge.CenterNode.ResetKeneticValues();
                edge.ControlNodeOne.ResetKeneticValues();
            }
            CompositionTarget.Rendering -= PositionAnimation_Rendering;
            OnPositionAnimationComplete();
            m_isAnimating = false;
        }

        private void PositionAnimation_Rendering(object sender, EventArgs e)
        {
            double totalKineticEnergy = 0;
            DummyNode dragNodeDummy = new DummyNode();
            dragNodeDummy.Position = DragPosition;

            foreach (VisualEdge edge in m_edges)
            {
                edge.CenterNode.NetForce = new Vector(0, 0);
                edge.ControlNodeOne.NetForce = new Vector(0, 0);
                edge.ControlNodeTwo.NetForce = new Vector(0, 0);
                
                CalculateEdgeRepulsion(edge, edge.CenterNode.Position, 10, 10);
                CalculateEdgeRepulsion(edge, edge.ControlNodeOne.Position, 10, 10);
                CalculateEdgeRepulsion(edge, edge.ControlNodeTwo.Position, 10, 10);

                //edge.CenterNode.NetForce += CalculateRepulsion(edge.CenterNode.Position, edge.ControlNodeOne.Position, 10, 10);
                //edge.ControlNodeOne.NetForce += CalculateRepulsion(edge.ControlNodeOne.Position, edge.CenterNode.Position, 10, 10);
                //edge.ControlNodeTwo.NetForce += CalculateRepulsion(
                foreach (VisualEdge otherEdge in m_edges.Where(ed => ed != edge))
                {
                    CalculateEdgeRepulsion(edge, otherEdge.CenterNode.Position, 10, 10);
                    CalculateEdgeRepulsion(edge, otherEdge.ControlNodeOne.Position, 10, 10);
                    CalculateEdgeRepulsion(edge, otherEdge.ControlNodeTwo.Position, 10, 10);
                    //edge.CenterNode.NetForce += CalculateRepulsion(edge.CenterNode.Position, otherEdge.CenterNode.Position, 10, 10);
                    //edge.CenterNode.NetForce += CalculateRepulsion(edge.CenterNode.Position, otherEdge.ControlNodeOne.Position, 10, 10);

                    //edge.ControlNodeOne.NetForce += CalculateRepulsion(edge.ControlNodeOne.Position, otherEdge.CenterNode.Position, 10, 10);
                    //edge.ControlNodeOne.NetForce += CalculateRepulsion(edge.ControlNodeOne.Position, otherEdge.ControlNodeOne.Position, 10, 10);
                }

                foreach (VisualNode node in AllVisualNodes(m_rootNode))
                {
                    CalculateEdgeRepulsion(edge, node.Position, 10, 10);
                    //edge.CenterNode.NetForce += CalculateRepulsion(edge.CenterNode.Position, node.Position, 10, 10);
                    //edge.ControlNodeOne.NetForce += CalculateRepulsion(edge.ControlNodeOne.Position, node.Position, 10, 10);
                }

                edge.ControlNodeOne.NetForce += CalculateAttraction(edge.ControlNodeOne.Position, edge.VisualNodeFrom.Position,
                    edge.Length / 4.0, 40);

                edge.ControlNodeOne.NetForce += CalculateAttraction(edge.ControlNodeOne.Position, edge.CenterNode.Position,
                    edge.Length / 4.0, 40);

                edge.CenterNode.NetForce += CalculateAttraction(edge.CenterNode.Position, edge.ControlNodeOne.Position,
                    edge.Length / 4.0, 40);

                edge.CenterNode.NetForce += CalculateAttraction(edge.CenterNode.Position, edge.ControlNodeTwo.Position,
                    edge.Length / 4.0, 40);

                edge.ControlNodeTwo.NetForce += CalculateAttraction(edge.ControlNodeTwo.Position, edge.CenterNode.Position,
                    edge.Length / 4.0, 40);

                edge.ControlNodeTwo.NetForce += CalculateAttraction(edge.ControlNodeTwo.Position, edge.VisualNodeTo.Position,
                    edge.Length / 4.0, 40);

                //edge.CenterNode.NetForce += CalculateAttraction(edge.CenterNode.Position, edge.VisualNodeFrom.Position, edge.Length / 3.0, 40);
                //edge.CenterNode.NetForce += CalculateAttraction(edge.CenterNode.Position, edge.ControlNodeOne.Position, edge.Length / 3.0, 40);
                //edge.ControlNodeOne.NetForce += CalculateAttraction(edge.ControlNodeOne.Position, edge.VisualNodeTo.Position, edge.Length / 3.0, 40);

                edge.CenterNode.Velocity = (edge.CenterNode.Velocity + m_timeStep * edge.CenterNode.NetForce) * m_damping;
                edge.CenterNode.Position += m_timeStep * edge.CenterNode.Velocity;
                //totalKineticEnergy += edge.CenterNode.Velocity.LengthSquared;

                edge.ControlNodeOne.Velocity = (edge.ControlNodeOne.Velocity + m_timeStep * edge.ControlNodeOne.NetForce) * m_damping;
                edge.ControlNodeOne.Position += m_timeStep * edge.ControlNodeOne.Velocity;
                //totalKineticEnergy += edge.ControlNodeOne.Velocity.LengthSquared;

                edge.ControlNodeTwo.Velocity = (edge.ControlNodeTwo.Velocity + m_timeStep * edge.ControlNodeTwo.NetForce) * m_damping;
                edge.ControlNodeTwo.Position += m_timeStep * edge.ControlNodeTwo.Velocity;
                //totalKineticEnergy += edge.ControlNodeTwo.Velocity.LengthSquared;
            }



            foreach (VisualNode node in AllVisualNodes(m_rootNode))
            {
                node.NetForce = new Vector(0, 0);

                //if (m_mouseRepulse)
                //    node.NetForce += CalculateRepulsion(node, m_mouseCharge);

                //if (m_mouseAttract)
                //    node.NetForce += CalculateAttraction(node, m_mouseCharge, m_edgeLength / 2, m_spring / 2);
                if (m_dragging && node == m_dragNode)
                {
                    m_dragNode.NetForce = CalculateAttraction(node.Position, m_dragDummy.Position, 0, 100);
                }
                else if (node == m_rootNode)
                {
                    node.NetForce = CalculateAttraction(node.Position, m_centerSpring.Position, 0, m_spring);
                }
                else
                {

                    List<DummyNode> sides = new List<DummyNode>() 
                        { new DummyNode(), new DummyNode(), new DummyNode(), new DummyNode() };

                    sides[0].Position = new Point(node.Position.X, (node.Position.Y - Math.Abs(node.Position.Y)));
                    sides[1].Position = new Point(node.Position.X, (node.Position.Y + Math.Abs(m_height - node.Position.Y)));
                    sides[2].Position = new Point((node.Position.X - Math.Abs(node.Position.X)), node.Position.Y);
                    sides[3].Position = new Point((node.Position.X + Math.Abs(m_width - node.Position.X)), node.Position.Y);

                    foreach (VisualNode otherNode in AllVisualNodes(m_rootNode).Where(n => n != node))
                    {
                        node.NetForce += CalculateRepulsion(node.Position, otherNode.Position, node.Charge, otherNode.Charge);
                    }
                    foreach (VisualNode connectedNode in node.AdjacentVisualNodes)
                    {
                        if (connectedNode == m_rootNode)
                        {
                            node.NetForce += CalculateAttraction(node.Position, connectedNode.Position, m_rootEdgeLength, m_spring);
                        }
                        else
                        {
                            node.NetForce += CalculateAttraction(node.Position, connectedNode.Position, m_edgeLength, m_spring);
                        }
                    }
                    foreach (var side in sides)
                    {
                        if (node.AdjacentVisualNodes.Count == 1)
                            side.Charge = 3 * node.Charge;
                        else
                            side.Charge = node.Charge;

                        node.NetForce += CalculateRepulsion(node.Position, side.Position, node.Charge, side.Charge);
                    }

                    foreach (VisualEdge edge in m_edges)
                    {
                        node.NetForce += CalculateRepulsion(node.Position, edge.CenterNode.Position, node.Charge / 3.0, edge.CenterNode.Charge / 3.0);
                        node.NetForce += CalculateRepulsion(node.Position, edge.ControlNodeOne.Position, node.Charge / 3.0, edge.ControlNodeOne.Charge / 3.0);
                        node.NetForce += CalculateRepulsion(node.Position, edge.ControlNodeTwo.Position, node.Charge / 3.0, edge.ControlNodeTwo.Charge / 3.0);
                    }
                }

                node.Velocity = (node.Velocity + m_timeStep * node.NetForce) * m_damping;
                node.Position += m_timeStep * node.Velocity;
                totalKineticEnergy += node.Mass * node.Velocity.LengthSquared;


            }
            if (totalKineticEnergy < m_kineticEnergyLimit)
            {
                Stop();
            }
         
        }

        public double TimeStep
        {
            get { return m_timeStep; }
            set { m_timeStep = value; }
        }
        public double Charge
        {
            get { return m_charge; }
            set 
            { 
                m_charge = value;
                OnPropertyChanged("Charge");
                Start(m_rootNode, m_width, m_height);
            }
        }
        public double EdgeLength
        {
            get { return m_edgeLength; }
            set 
            { 
                m_edgeLength = value;
                OnPropertyChanged("EdgeLength");
                Start(m_rootNode, m_width, m_height);
            }
        }
        public double Spring
        {
            get { return m_spring; }
            set 
            { 
                m_spring = value;
                OnPropertyChanged("Spring");
                Start(m_rootNode, m_width, m_height);
            }
        }
        public double Damping
        {
            get { return m_damping; }
            set
            {
                m_damping = value;
                OnPropertyChanged("Damping");
                Start(m_rootNode, m_width, m_height);
            }
        }
        public bool IsAnimating
        {
            get { return m_isAnimating; }
            set { m_isAnimating = value; }
        }

        private double m_timeStep;
        private double m_charge;
        private double m_spring;
        private double m_edgeLength;
        private double m_rootEdgeLength;
        private double m_damping;
        private bool m_isAnimating;

        private double m_width;
        private double m_height;
        private int m_numberEdges;
        private double m_kineticEnergyLimit;
        private DummyNode m_mouseCharge;
        private DummyNode m_centerSpring;
        private VisualNode m_rootNode;

        // Get all the visual nodes (including the root)
        private static IEnumerable<VisualNode> AllVisualNodes(VisualNode root)
        {
            yield return root;
            foreach (VisualNode node in root.AdjacentVisualNodes)
                yield return node;
        }

        // Calculate the attraction of two visual nodes (or dummy nodes)
        private static Vector CalculateAttraction(Point sourcePosition, Point targetPosition,
            double edgeLength, double spring)
        {
            Vector force = (sourcePosition - targetPosition);
            double displacement = force.Length - edgeLength;

            if (force == new Vector(0, 0))
                return force;

            force.Normalize();
            force.Negate();

            return spring * force * displacement;
        }

        private static void CalculateEdgeAttraction(VisualEdge edge, Point targetPosition, double edgeLength, double spring)
        {
            edge.CenterNode.NetForce += CalculateAttraction(edge.CenterNode.Position, targetPosition, edgeLength, spring);
            edge.ControlNodeOne.NetForce += CalculateAttraction(edge.ControlNodeOne.Position, targetPosition, edgeLength, spring);
            edge.ControlNodeTwo.NetForce += CalculateAttraction(edge.ControlNodeTwo.Position, targetPosition, edgeLength, spring);
        }

        // Calculate the repulsion of two visual nodes (or dummy nodes)
        private static Vector CalculateRepulsion(Point sourcePosition, Point targetPosition,
            double sourceCharge, double targetCharge)
        {
            Vector force = (sourcePosition - targetPosition);

            if (force == new Vector(0, 0))
                return force;

            return (sourceCharge * targetCharge * force) / force.LengthSquared;
        }

        private static void CalculateEdgeRepulsion(VisualEdge edge, Point targetPosition, double sourceCharge, double targetCharge)
        {
            edge.CenterNode.NetForce += CalculateRepulsion(edge.CenterNode.Position, targetPosition, sourceCharge, targetCharge);
            edge.ControlNodeOne.NetForce += CalculateRepulsion(edge.ControlNodeOne.Position, targetPosition, sourceCharge, targetCharge);
            edge.ControlNodeTwo.NetForce += CalculateRepulsion(edge.ControlNodeTwo.Position, targetPosition, sourceCharge, targetCharge);
        }

        //public void MoveMouseCharge(Point mouseLocation)
        //{
        //    m_mouseCharge.Position = mouseLocation;
        //}


        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string info)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(info));
        }

        #endregion
    }
}
